package authblog

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"blogcenter/internal/middleware"
	"blogcenter/internal/model"
	"blogcenter/internal/svc"
	"blogcenter/internal/types"

	"github.com/zeromicro/go-zero/core/logx"
	"github.com/zeromicro/go-zero/core/stores/redis"
	"gorm.io/gorm"
)

type GetBlogLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

const (
	blogCacheKeyPrefix    = "blog:"          // 博客缓存键前缀
	blogReadCountKey      = "blog:read:"     // 阅读量计数键前缀
	blogLikeKeyPrefix     = "blog:like:"     // 点赞状态键前缀
	cacheExpiration       = 30 * time.Minute // 缓存过期时间
	readCountSyncInterval = 10 * time.Second // 阅读量同步间隔
)

func NewGetBlogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetBlogLogic {
	return &GetBlogLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *GetBlogLogic) GetBlog(req *types.IdRequest) (resp *types.BlogDetailResponse, err error) {
	// 参数校验
	if req.Id <= 0 {
		return nil, errors.New("无效的博客ID")
	}

	// 从上下文中获取当前用户ID
	currentUserId, _ := l.ctx.Value(middleware.UserIdKey).(int)

	// 1. 尝试从缓存获取
	cacheKey := fmt.Sprintf("%s%d", blogCacheKeyPrefix, req.Id)
	cachedData, err := l.getFromCache(cacheKey, currentUserId)
	if err == nil && cachedData != nil {
		// 增加阅读量计数（异步）
		go l.incrementReadCount(req.Id)
		return cachedData, nil
	}
	if err != nil && err != redis.Nil {
		logx.Errorf("从缓存获取博客失败, key: %s, error: %v", cacheKey, err)
	}

	// 2. 缓存未命中，从数据库查询
	blog, images, err := l.svcCtx.BlogModel.FindWithImages(l.ctx, uint(req.Id))
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			go func() {
				if err := l.setToCache(cacheKey, resp); err != nil {
					logx.Errorf("更新缓存失败, key: %s, error: %v", cacheKey, err)
				}
			}() // 设置空数据到缓存，防止缓存穿透
			return nil, errors.New("博客不存在")
		}
		logx.Errorf("查询博客详情失败, blogId: %d, error: %v", req.Id, err)
		return nil, errors.New("获取博客详情失败")
	}

	// 3. 获取用户点赞状态
	var isLiked bool
	if currentUserId > 0 {
		// 先尝试从Redis获取点赞状态
		likeKey := fmt.Sprintf("%s%d:%d", blogLikeKeyPrefix, req.Id, currentUserId)
		if val, err := l.svcCtx.Redis.Get(likeKey); err == nil && val == "1" {
			isLiked = true
		} else if err != nil && err != redis.Nil {
			logx.Errorf("获取Redis点赞状态失败, key: %s, error: %v", likeKey, err)
		}

		// Redis未命中则查询数据库
		if !isLiked {
			var likeRecord model.BlogLike
			err := l.svcCtx.DB.Where("blog_id = ? AND user_id = ?", req.Id, currentUserId).
				First(&likeRecord).Error
			if err == nil && likeRecord.LikeAction == 1 {
				isLiked = true
				// 异步写入Redis缓存
				go func() {
					if err := l.svcCtx.Redis.Setex(likeKey, "1", int((24 * time.Hour).Seconds())); err != nil {
						logx.Errorf("写入点赞状态到Redis失败, key: %s, error: %v", likeKey, err)
					}
				}()
			} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
				logx.Errorf("查询数据库点赞状态失败, blogId: %d, userId: %d, error: %v",
					req.Id, currentUserId, err)
			}
		}
	}

	// 4. 构造响应数据
	resp = &types.BlogDetailResponse{
		BaseResponse: types.BaseResponse{
			Code:    200,
			Message: "获取成功",
		},
		Data: types.Blog{
			Id:         int(blog.ID),
			Title:      blog.Title,
			Content:    blog.Content,
			LikeNum:    blog.LikeNum,
			CommentNum: blog.CommentNum,
			Place:      blog.Place,
			JoyNum:     blog.JoyNum,
			JoyFeeling: blog.JoyFeeling,
			CreateTime: blog.CreatedAt.Format("2006-01-02 15:04:05"),
			Photo: types.PhotoData{
				Url: images,
			},
			UserId:  blog.UserId,
			ReadNum: blog.ReadNum,
			Liked:   isLiked, // 添加点赞状态
		},
	}

	// 5. 异步更新缓存
	go func() {
		if err := l.setToCache(cacheKey, resp); err != nil {
			logx.Errorf("更新缓存失败, key: %s, error: %v", cacheKey, err)
		}
	}()

	// 6. 增加阅读量计数（异步）
	go l.incrementReadCount(req.Id)

	// 异步同步到 Gorse
	go func() {
		ctx := context.Background()
		err := l.svcCtx.GorseClient.FeedBackGorse(ctx, int(currentUserId), int(req.Id), "read", time.Now())
		if err != nil {
			logx.Errorf("同步阅读到Gorse失败: %v", err)
		}
	}()
	return resp, nil
}

// 修改后的从缓存获取数据方法（添加用户点赞状态）
func (l *GetBlogLogic) getFromCache(key string, userId int) (*types.BlogDetailResponse, error) {
	data, err := l.svcCtx.Redis.Get(key)
	if err != nil {
		return nil, err
	}

	var resp types.BlogDetailResponse
	if err := json.Unmarshal([]byte(data), &resp); err != nil {
		return nil, fmt.Errorf("反序列化缓存数据失败: %v", err)
	}

	// 如果用户已登录，补充点赞状态
	if userId > 0 {
		likeKey := fmt.Sprintf("%s%d:%d", blogLikeKeyPrefix, resp.Data.Id, userId)
		if val, err := l.svcCtx.Redis.Get(likeKey); err == nil && val == "1" {
			resp.Data.Liked = true
		}
	}

	return &resp, nil
}

// 设置数据到缓存（保持不变）
func (l *GetBlogLogic) setToCache(key string, data *types.BlogDetailResponse) error {
	jsonData, err := json.Marshal(data)
	if err != nil {
		return fmt.Errorf("序列化缓存数据失败: %v", err)
	}

	if err := l.svcCtx.Redis.Setex(key, string(jsonData), int(cacheExpiration.Seconds())); err != nil {
		return err
	}

	return nil
}

// 增加阅读量计数（保持不变）
func (l *GetBlogLogic) incrementReadCount(blogId int) {
	readCountKey := fmt.Sprintf("%s%d", blogReadCountKey, blogId)

	// 使用Redis的INCR命令原子性增加计数
	_, err := l.svcCtx.Redis.Incr(readCountKey)
	if err != nil {
		logx.Errorf("增加博客阅读量计数失败, blogId: %d, error: %v", blogId, err)
	}

	// 设置过期时间，防止长期不访问的数据占用内存
	err = l.svcCtx.Redis.Expire(readCountKey, int((24 * time.Hour).Seconds()))
	if err != nil {
		logx.Errorf("设置阅读量计数过期时间失败, blogId: %d, error: %v", blogId, err)
	}
}

// 其他保持不变...

// 启动阅读量同步服务（在服务启动时调用）
func StartReadCountSyncService(svcCtx *svc.ServiceContext) {
	ticker := time.NewTicker(readCountSyncInterval)
	defer ticker.Stop()

	for range ticker.C {
		syncReadCountsToDB(svcCtx)
	}
}

// 同步阅读量到数据库
func syncReadCountsToDB(svcCtx *svc.ServiceContext) {
	// 获取所有阅读量计数键
	keys, err := svcCtx.Redis.Keys(fmt.Sprintf("%s*", blogReadCountKey))
	if err != nil {
		logx.Errorf("获取阅读量计数键失败: %v", err)
		return
	}

	for _, key := range keys {
		// 从键名中提取博客ID
		blogIdStr := strings.TrimPrefix(key, blogReadCountKey)
		blogId, err := strconv.Atoi(blogIdStr)
		if err != nil {
			logx.Errorf("解析博客ID失败, key: %s, error: %v", key, err)
			continue
		}

		// 获取并重置阅读量计数
		countStr, err := svcCtx.Redis.Get(key)
		if err != nil {
			logx.Errorf("获取阅读量计数失败, blogId: %d, error: %v", blogId, err)
			continue
		}

		count, err := strconv.Atoi(countStr)
		if err != nil {
			logx.Errorf("解析阅读量计数失败, blogId: %d, count: %s, error: %v", blogId, countStr, err)
			continue
		}

		// 如果计数为0，跳过
		if count == 0 {
			continue
		}

		// 重置计数为0，并设置6小时TTL
		err = svcCtx.Redis.Setex(key, "0", int((6 * time.Hour).Seconds()))
		if err != nil {
			logx.Errorf("重置阅读量计数失败, blogId: %d, error: %v", blogId, err)
			continue
		}

		// 更新数据库
		err = svcCtx.BlogModel.IncrementReadCount(context.Background(), uint(blogId), count)
		if err != nil {
			logx.Errorf("更新数据库阅读量失败, blogId: %d, count: %d, error: %v", blogId, count, err)
			// 如果更新失败，恢复计数
			_, err = svcCtx.Redis.Incrby(key, int64(count))
			if err != nil {
				logx.Errorf("恢复阅读量计数失败, blogId: %d, count: %d, error: %v", blogId, count, err)
			}
			continue
		}

		// 更新缓存中的博客数据（新增部分）
		updateCacheReadCount(svcCtx, blogId, count)
	}
}

// 更新缓存中的阅读量
func updateCacheReadCount(svcCtx *svc.ServiceContext, blogId int, increment int) {
	cacheKey := fmt.Sprintf("%s%d", blogCacheKeyPrefix, blogId)

	// 1. 从缓存获取现有博客数据
	data, err := svcCtx.Redis.Get(cacheKey)
	if err != nil {
		if err != redis.Nil {
			logx.Errorf("获取博客缓存失败, key: %s, error: %v", cacheKey, err)
		}
		return
	}

	// 2. 解析缓存数据
	var blogResp types.BlogDetailResponse
	if err := json.Unmarshal([]byte(data), &blogResp); err != nil {
		logx.Errorf("解析博客缓存数据失败, key: %s, error: %v", cacheKey, err)
		return
	}

	// 3. 更新阅读量
	blogResp.Data.ReadNum += increment

	// 4. 重新序列化并写回缓存
	jsonData, err := json.Marshal(blogResp)
	if err != nil {
		logx.Errorf("序列化博客数据失败, blogId: %d, error: %v", blogId, err)
		return
	}

	if err := svcCtx.Redis.Setex(cacheKey, string(jsonData), int(cacheExpiration.Seconds())); err != nil {
		logx.Errorf("更新博客缓存失败, key: %s, error: %v", cacheKey, err)
	}
}
